<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>鼠标粒子特效</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      *{box-sizing:border-box;}html,body{height:100%;}body{overflow:hidden;display:grid;color:white;background:black;}
    </style>
  </head>
  <body>
    <pointer-particles></pointer-particles>
    <script>class PointerParticle{constructor(spread,speed,component){const{ctx,pointer,hue}=component;this.ctx=ctx;this.x=pointer.x;this.y=pointer.y;this.mx=pointer.mx*0.1;this.my=pointer.my*0.1;this.size=Math.random()+1;this.decay=0.01;this.speed=speed*0.08;this.spread=spread*this.speed;this.spreadX=(Math.random()-0.5)*this.spread-this.mx;this.spreadY=(Math.random()-0.5)*this.spread-this.my;this.color=`hsl(${hue}deg 90%60%)`}draw(){this.ctx.fillStyle=this.color;this.ctx.beginPath();this.ctx.arc(this.x,this.y,this.size,0,Math.PI*2);this.ctx.fill()}collapse(){this.size-=this.decay}trail(){this.x+=this.spreadX*this.size;this.y+=this.spreadY*this.size}update(){this.draw();this.trail();this.collapse()}}class PointerParticles extends HTMLElement{static register(tag='pointer-particles'){if('customElements'in window){customElements.define(tag,this)}}static css=`:host{display:grid;width:100%;height:100%;pointer-events:none}`;constructor(){super();this.canvas;this.ctx;this.fps=60;this.msPerFrame=1000/this.fps;this.timePrevious;this.particles=[];this.pointer={x:0,y:0,mx:0,my:0,};this.hue=0}connectedCallback(){const canvas=document.createElement('canvas');const sheet=new CSSStyleSheet();this.shadowroot=this.attachShadow({mode:'open'});sheet.replaceSync(PointerParticles.css);this.shadowroot.adoptedStyleSheets=[sheet];this.shadowroot.append(canvas);this.canvas=this.shadowroot.querySelector('canvas');this.ctx=this.canvas.getContext('2d');this.setCanvasDimensions();this.setupEvents();this.timePrevious=performance.now();this.animateParticles()}createParticles(event,{count,speed,spread}){this.setPointerValues(event);for(let i=0;i<count;i++){this.particles.push(new PointerParticle(spread,speed,this))}}setPointerValues(event){this.pointer.x=event.x-this.offsetLeft;this.pointer.y=event.y-this.offsetTop;this.pointer.mx=event.movementX;this.pointer.my=event.movementY}setupEvents(){const parent=this.parentNode;parent.addEventListener('click',(event)=>{this.createParticles(event,{count:300,speed:Math.random()+1,spread:Math.random()+50,})});parent.addEventListener('pointermove',(event)=>{this.createParticles(event,{count:20,speed:this.getPointerVelocity(event),spread:1,})});window.addEventListener('resize',()=>this.setCanvasDimensions())}getPointerVelocity(event){const a=event.movementX;const b=event.movementY;const c=Math.floor(Math.sqrt(a*a+b*b));return c}handleParticles(){for(let i=0;i<this.particles.length;i++){this.particles[i].update();if(this.particles[i].size<=0.1){this.particles.splice(i,1);i--}}}setCanvasDimensions(){const rect=this.parentNode.getBoundingClientRect();this.canvas.width=rect.width;this.canvas.height=rect.height}animateParticles(){requestAnimationFrame(()=>this.animateParticles());const timeNow=performance.now();const timePassed=timeNow-this.timePrevious;if(timePassed<this.msPerFrame)return;const excessTime=timePassed%this.msPerFrame;this.timePrevious=timeNow-excessTime;this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);this.hue=this.hue>360?0:(this.hue+=3);this.handleParticles()}}PointerParticles.register();</script>
  </body>
</html>